#include "DepthTexture.h"
#include "../Utilities.h"

#include <string>
#include <sstream>
#include <dxerr.h>
#include <atlbase.h>


HRESULT CheckDXResult(HRESULT result, const wchar_t* call)
{
	if(FAILED(result))
	{
		std::wstring error = DXGetErrorString(result);
		std::wstring description = DXGetErrorDescription(result);
		std::wstringstream message;
		message << L"DX Call Failed!" << std::endl;
		message << L"  Call: " << call << std::endl;
		//message << L"  Location: " << file << L"(" << line << L")" << std::endl;
		message << L"  Error: " << error << std::endl;
		message << L"  Description: " << description << std::endl;
		throw Exception(message.str());
	}
	return result;
}
#define SafeCall(Call) CheckDXResult((Call), L#Call)


DepthTexture::DepthTexture( IDirect3DDevice9* device, IEnvironment& environment, IShaderSystem& shaderSystem, const std::string& shaderPath )
	: device(device)
	, environment(environment)
	, shaderSystem(shaderSystem)
	, depthTextureSurface(NULL)
	, screenMaskCount(20)
	, depthRenderShader(NULL)
	, blackRenderShader(NULL)
	, screenMaskBatch(NULL)
	, clearMask(NULL)
	, backBufferTexture(NULL)
{
	IDirect3DSurface9* backBuffer;
	device->GetRenderTarget(0, &backBuffer);
	backBuffer->GetDesc(&backBufferDescription);
	backBuffer->Release();

	try
	{
		SafeCall(device->CreateTexture(backBufferDescription.Width, backBufferDescription.Height, 1, 
			D3DUSAGE_DEPTHSTENCIL, FOURCC_INTZ, 
			D3DPOOL_DEFAULT, &texture, 
			NULL));
		SafeCall(texture->GetSurfaceLevel(0, &depthTextureSurface)); 
		type = r_DepthTextureType::INTZ;
	}
	catch (...)
	{
		try
		{
			SafeCall(device->CreateTexture(backBufferDescription.Width, backBufferDescription.Height, 1, 
				D3DUSAGE_DEPTHSTENCIL, FOURCC_RAWZ, 
				D3DPOOL_DEFAULT, &texture, 
				NULL));
			SafeCall(texture->GetSurfaceLevel(0, &depthTextureSurface)); 
			type = r_DepthTextureType::RAWZ;
		}
		catch (...)
		{
			type = r_DepthTextureType::DepthPeeling;

			::D3DXMatrixLookAtLH(
				&viewMatrix,
				&D3DXVECTOR3(0.0f, 0.0f, 0.0f),
				&D3DXVECTOR3(0.0f, 0.0f, 1.0f),
				&D3DXVECTOR3(0.0f, 1.0f, 0.0f)
				);
			::D3DXMatrixPerspectiveFovLH(
				&projMatrix,
				D3DXToRadian(90),
				1.0,
				1.0f,
				2.0f
				);
			::D3DXMatrixIdentity(&worldMatrix);

			clearMask = new ScreenMask();
			clearMask->Create(device, 0, 0, 1, 1);

			screenMaskBatch = new BatchScreenMask(device, screenMaskCount * 4);
			for(int i = 0; i < screenMaskCount; ++i)
			{
				float lf_Depth = 1.0f - (i * (1.0f / screenMaskCount));
				float lf_RealDepth = static_cast<float>(this->CalcZDepth(lf_Depth));

				ScreenMask* screenMask = new ScreenMask(true);
				screenMask->Create(device, 0, 0, 1, 1);
				screenMask->SetTextureCoords(lf_Depth, lf_Depth, lf_Depth, lf_Depth, 1);
				screenMask->SetZ(lf_RealDepth);

				screenMaskList.push_back(screenMask);
				screenMaskBatch->AddScreenMaskData(*screenMask, device, i == 0, (i + 1) == screenMaskCount);
			}

			SafeCall(device->CreateTexture(backBufferDescription.Width,
					backBufferDescription.Height,
					1,
					D3DUSAGE_RENDERTARGET,
					backBufferDescription.Format,
					D3DPOOL_DEFAULT,
					&backBufferTexture,
					NULL));

			depthRenderShader = this->LoadShader("RenderDepth.hlsl", shaderPath);
			blackRenderShader = this->LoadShader("RenderBlack.hlsl", shaderPath);
		}
	}
	

	this->BindDepthBuffer();
}


DepthTexture::~DepthTexture()
{
	SafeRelease(depthTextureSurface);
	SafeRelease(texture);
	SafeRelease(backBufferTexture);

	for(unsigned int i = 0; i < screenMaskList.size(); ++i)
		SafeDelete (screenMaskList[i]);
	screenMaskList.clear();

	SafeDelete(screenMaskBatch);
}


void DepthTexture::BindDepthBuffer()
{
	if (type == r_DepthTextureType::DepthPeeling)
		return;

	SafeCall(device->SetDepthStencilSurface(depthTextureSurface));
}


void DepthTexture::CalculateDepthBufferData()
{
	if (type != r_DepthTextureType::DepthPeeling)
		return;

	RECT lr_Rect;
	lr_Rect.left = lr_Rect.top = 0;
	lr_Rect.right = backBufferDescription.Width;
	lr_Rect.bottom = backBufferDescription.Height;

	this->CopyRenderTarget(backBufferTexture, &lr_Rect);

	this->Render(clearMask, blackRenderShader);	
	this->Render(screenMaskBatch, depthRenderShader);

	this->CopyRenderTarget(backBufferTexture, &lr_Rect);
}


double DepthTexture::CalcZDepth( double ad_PercentageDepth )
{
	D3DXVECTOR3 lr_Final;
	D3DXVECTOR3 lr_Depth(0, 0, static_cast<float>(1 + ad_PercentageDepth));

	::D3DXVec3Project(
		&lr_Final,
		&lr_Depth,
		&viewPort,
		&projMatrix,
		&viewMatrix,
		&worldMatrix
		);

	return lr_Final.z;
}


void DepthTexture::CopyRenderTarget( IDirect3DTexture9* ar_Texture_, RECT* ar_Rect_ )
{
	if (!ar_Texture_)
		throw Exception(L"DepthTexture: Destination texture is NULL.\n");

	// get surfaces
	IDirect3DSurface9* lr_Dest_ = NULL;
	ar_Texture_->GetSurfaceLevel(0, &lr_Dest_);
	if (!lr_Dest_)
		throw Exception(L"DepthTexture: Unable to get surface from destination texture.\n");

	IDirect3DSurface9* lr_Source_ = NULL;
	device->GetRenderTarget(0, &lr_Source_);	
	if (!lr_Source_)
		throw Exception(L"DepthTexture: Unable to get surface from backbuffer.\n");

	HRESULT lh_Result = device->StretchRect(lr_Source_, ar_Rect_, lr_Dest_, ar_Rect_, D3DTEXF_NONE);
	if (FAILED(lh_Result))
		throw Exception(L"DepthTexture: Failed to update our textures\n");

	lr_Dest_->Release();
	lr_Source_->Release();
}


void DepthTexture::Render( IRenderable* ar_ScreenMask_, IShader* ar_Shader_ )
{
	ar_ScreenMask_->BeginRender(device);

	unsigned int Passes = ar_Shader_->get_PassCount();
	ar_Shader_->Begin();
	for(unsigned int CurrentPass = 0; CurrentPass<Passes; ++CurrentPass)
	{
		RenderPassReturnValue::Enumeration ReturnValue;
		do
		{
			ReturnValue = ar_Shader_->BeginPass(CurrentPass);
			if (ReturnValue != RenderPassReturnValue::RenderAndCallNot)
			{
				ar_ScreenMask_->Render(device);
			}
		} while(ReturnValue == RenderPassReturnValue::RenderAndCallAgain);
		ar_Shader_->EndPass(); 
	}
	ar_Shader_->End();
}


IShader* DepthTexture::LoadShader( const std::string& as_FileName, const std::string& as_Path )
{
	USES_CONVERSION;
	std::string ls_WalhallPath = W2A(environment.get_WalhallPath().c_str());
	std::string ls_File = ls_WalhallPath + as_Path + "\\" + as_FileName;
	return shaderSystem.LoadShader(as_FileName, ls_File);
}
